<!DOCTYPE html>
<html lang="zh-cn">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>H进制用矩阵表示，求解方程</title>

    <!-- <script id="MathJax-script" async src="https://cdn.staticfile.org/mathjax/3.2.2/es5/tex-chtml.min.js"></script> -->
    <script id="MathJax-script" src="./mathjax/tex-chtml.min.js"></script>
</head>
<style>
    :root {
        --EQUTION_SIZE: 20rem;
    }

    body {
        font-family: Consolas, "Courier New", monospace;
    }

    #D2 {
        display: flex;
        justify-content: space-evenly;
        flex-wrap: wrap;
        flex-direction: row;
        align-content: space-between;
        align-items: center;

        div {
            width: var(--EQUTION_SIZE);
            margin-top: 1rem;
        }
    }

    #D3 {
        display: block;
    }

    #D4 {
        display: flex;
        flex: 1 1 auto;
        flex-flow: row wrap;
        justify-content: space-around;
        align-items: start;

        div {
            margin: 1rem 1rem;
        }

        & div:nth-child(2n+1) {
            background-color: #ffe;
        }

        & div:nth-child(2n) {
            background-color: #eff;
        }
    }
</style>

<body>
</body>

<script>
    /**
     *  quadratic   二次方项
     *  linear      一次方项
     */


    const MX_LENGTH = 3;
    const MX_LETTERS = "ABCDEF".split("");
    const MX_NUMS = '012345678'.split("");
    const CD = "•"; //splitter
    const ERR_STR = "Error";
    const REG_1 = /\(\w\d-\w\d\)/;

    // 3n+1 core
    const PREV_MAP_1 = {
        0: { 0: 0, 3: 0, 1: 3, 4: 3, 2: 1, 5: 1, },
        1: { 0: 0, 3: 0, 1: 3, 4: 3, 2: 1, 5: 1, },
        2: { 0: 0, 3: 0, 1: 3, 4: 3, 2: 1, 5: 1, },
        3: { 0: 4, 3: 4, 1: 2, 4: 2, 2: 5, 5: 5, },
        4: { 0: 4, 3: 4, 1: 2, 4: 2, 2: 5, 5: 5, },
        5: { 0: 4, 3: 4, 1: 2, 4: 2, 2: 5, 5: 5, },
    };


    /**
     * !! NOTICE: the frequency of the count of same param is not the same !!
     * ！！注意：统计相同参数出现的等式频率是不相等的！！
     */
    ((x) => {
        if (!x) return null;

        let eqs = [];
        for (const [i, o] of Object.entries(PREV_MAP_1)) {
            for (const [j, v] of Object.entries(o)) {
                // console.log("run MX_EQ ", i, j, v);
                eqs.push([i, j, v].join(","));
            }
        }

        console.log("PREV_MAP_1 equantions \n", eqs.join(" | "));

        let cln = {};
        for (let i = 0; i < 6; i++) {
            let ii = i.toString();
            cln[ii] = 0;
            eqs.forEach(e => {
                if (e.indexOf(ii) > -1) cln[ii]++
            });
        }

        console.log("PREV_MAP_1 static params \n", cln);
    })(true);

    // square matrixes
    const MX_SQS = (() => {
        let sqs = [];
        for (const l of MX_LETTERS) {
            let os = MX_NUMS.map(x => l + x);
            sqs.push([
                os[0], os[1], os[2],
                os[3], os[4], os[5],
                os[6], os[7], os[8],
            ]);
        }
        return sqs;
    })();

    // for console
    const printSQS = (mx) => {
        let s = "[\n"
        s += `\t[${mx[0]} ${mx[1]} ${mx[2]}]\n`
        s += `\t[${mx[3]} ${mx[4]} ${mx[5]}]\n`
        s += `\t[${mx[6]} ${mx[7]} ${mx[8]}]\n`
        s += "]\n";
        return s;
    };

    // for HTML Body
    const drawSQS = (mx) => {
        let s = "\\begin{bmatrix}\n";
        s += `\t${mx[0]} & ${mx[1]} & ${mx[2]} \\\\\n`;
        s += `\t${mx[3]} & ${mx[4]} & ${mx[5]} \\\\\n`;
        s += `\t${mx[6]} & ${mx[7]} & ${mx[8]} \\\\\n`;
        s += "\\end{bmatrix}\n &nbsp;&nbsp;";
        return s.replaceAll(/([A-G])(\d)/g, "$1_{$2}");
    };

    // print matrix LaTeX on HTML Body
    (() => {
        let s = "$$";
        for (const sq of MX_SQS) {
            // console.log(printSQS(sq));
            s += `${drawSQS(sq)}`;
        }
        s += "$$";

        const D = document.createElement('div');
        D.id = "D1";
        D.innerHTML = s;
        document.body.appendChild(D);
    })();

    //orders of params of matrixes
    const MX_P_ORD = (() => {
        const cnt = MX_LETTERS.length * MX_NUMS.length;
        if (MX_LETTERS.length * MX_NUMS.length >= 64) {
            alert("too many params");
            throw new Error("The number of params is too large");
            return null;
        }

        const ord = new Map();

        for (const [i, l] of MX_LETTERS.entries()) {
            for (const n of MX_NUMS) {
                ord.set(l + n, 10 * (1 + i) + (+n));
            }
        }

        console.log('MX_P_ORD', ord);

        return ord;
    })();

    // mutiply two params ,step 1
    const mtPm1 = (a, b) => {
        if (MX_P_ORD.get(a) - MX_P_ORD.get(b) > 0) {
            return b + CD + a;
        }
        return a + CD + b;
    };

    // mutiply two matrixes
    const mtMX = (i, j) => {
        const m1 = MX_SQS[i], m2 = MX_SQS[j];
        const x = mtPm1;
        let mx = [];
        for (let r = 0; r < MX_LENGTH; r++) {
            for (let c = 0; c < MX_LENGTH; c++) {
                let s = [];
                for (let k = 0; k < MX_LENGTH; k++) {
                    s.push(x(m1[r * MX_LENGTH + k], m2[c + k * MX_LENGTH]));
                }
                mx.push(s.join('+'));
            }
        }
        return mx;
    }

    // Equations of Matrix multiplication
    const MX_EQUATIONS_1 = (() => {
        let eqs = [];
        for (const [i, o] of Object.entries(PREV_MAP_1)) {
            for (const [j, v] of Object.entries(o)) {
                // console.log("run MX_EQ ", i, j, v);
                let mx = mtMX(+i, +j)
                for (let k = 0; k < MX_LENGTH * MX_LENGTH; k++) {
                    eqs.push(mx[k] + "=" + MX_SQS[v][k]);
                }
            }
        }
        return eqs;
    })();

    // x == false means do not show
    ((x) => {
        if (!x) return null;
        let s = "<div>\\(";
        for (const sq of MX_EQUATIONS_1) {
            s += sq + "\\)</div><div>\\(";
        }
        s += "\\)</div>";

        const D = document.createElement('div');
        D.id = "D2";
        D.innerHTML = s.replaceAll(/([A-G])(\d)/g, "$1_{$2}").replaceAll(CD, " \\cdot ");
        document.body.appendChild(D);
    })(false);

    const omit1 = (v) => v == 1 ? '' : v.toString();

    // Add up equations if it contains x
    const addUpEQ = (x) => {
        let eqs = [];
        for (const sq of MX_EQUATIONS_1) {
            if (sq.indexOf(x) >= 0) eqs.push(sq);
        }
        // console.log(eqs);

        // split left and right side of equation
        let left = {}, right = {};
        for (const eq of eqs) {
            [l, r] = eq.split("=");
            // add up quadratic
            let q = l.split("+");
            for (const qq of q) {
                // if (left[qq]) { left[qq]++; } else { left[qq] = 1; }
                left[qq] = (left[qq] ?? 0) + 1;
            }
            // if (right[r]) { right[r]++; } else { right[r] = 1; }
            right[r] = (right[r] ?? 0) + 1;
        }

        // sort for tidy
        let sl = Object.entries(left).sort((a, b) => {
            if (b[1] !== a[1]) return b[1] - a[1];
            return a[0].localeCompare(b[0]);
        })

        let sr = Object.entries(right).sort((a, b) => {
            if (b[1] !== a[1]) return b[1] - a[1];
            return a[0].localeCompare(b[0]);
        })

        let eql1 = { t: [], o: [] }, eql2 = [];
        let eql0 = "", eql3 = "", eql4 = "";
        let pt2 = new RegExp(`${x[0]}\\d${CD}${x[0]}\\d`);
        for (const [k, v] of sl) {
            if (k === (x + CD + x)) {
                eql0 = omit1(v) + x + "^2";
                continue;
            }

            if (k.includes(x)) {
                eql2.push(omit1(v) + k.replace(x, "").replace(CD, ""));
                continue;
            }

            if (pt2.test(k)) {
                eql1.t.push(omit1(v) + k);
                continue;
            }

            ix = k.indexOf(x[0]);

            if (ix === -1) {
                eql1.o.push(omit1(v) + k);
                continue;
            }

            d = k.substring(ix, ix + 2);

            if (!eql1[d]) {
                eql1[d] = [];
            }

            eql1[d].push(omit1(v) + k.replace(d, "").replace(CD, ""));
        }


        for (const [k, v] of sr) {
            if (k === x) {
                eql3 += '-' + omit1(v) + k;
            } else {
                eql4 += '+' + omit1(v) + k;
            }
        }

        let rtn = "";
        // left side of final equation
        rtn = eql0; // quadratic
        rtn += "+" + x + "(" + eql2.join("+") + ")"; // linear
        rtn += eql3;
        // right side of final equation
        rtn += "=" + eql4; // linear of other
        rtn += "-" + "(" + eql1['t'].join("+") + ")"; // quadratic of same row or column
        rtn += "-" + "(" + eql1['o'].join("+") + ")"; // quadratic of other

        for (k in eql1) {
            if (k === 'o' || k === 't') continue;
            rtn += "-" + k + "(" + eql1[k].join("+") + ")";
        }

        return rtn;
    }

    const drawEQ = (eqStr) => {
        return eqStr.replaceAll('=', '\\\\ \\quad =')
            .replaceAll('-', '\\\\ \\qquad -')
            .replaceAll(/([A-G])(\d)/g, "$1_{$2}");
    }

    // convenient for copy equation 
    ((x) => {
        if (!x) return;
        let s = "<p>";
        MX_SQS.flat().forEach((x) => {
            s += addUpEQ(x) + "</p><p>";
        });
        s += "</p>";

        const D = document.createElement('div');
        D.id = "D3";
        D.innerHTML = s;
        document.body.appendChild(D);
    })(false);

    // draw tidy equations order by parameter's order
    ((x) => {
        if (!x) return;
        let s = "$$ \\begin{cases}";
        MX_SQS.flat().forEach((x) => {
            s += drawEQ(addUpEQ(x)) + "\\".repeat(6);
        });

        s += "\\end{cases} $$";

        const D = document.createElement('div');
        D.id = "D3";
        D.innerHTML = s;
        document.body.appendChild(D);
    })(false);

    /**
     * 编辑距离
     * computes the edit distance between two strings
     * @param {string} s1
     * @param {string} s2
     * @@returns {number} 0 if the strings are the same,
     *  else the minimum number of operations required to transform one string into the other
     */
    const levenshteinDistance = (s1, s2) => {
        const m = s1.length;
        const n = s2.length;

        // Create a 2D array with (m+1) rows and (n+1) columns
        const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));

        for (let i = 0; i <= m; i++) {
            dp[i][0] = i; // deletion cost
        }

        for (let j = 0; j <= n; j++) {
            dp[0][j] = j; // insertion cost
        }

        for (let i = 1; i <= m; i++) {
            for (let j = 1; j <= n; j++) {
                if (s1[i - 1] === s2[j - 1]) {
                    dp[i][j] = dp[i - 1][j - 1]; // no additional cost
                } else {
                    dp[i][j] = 1 + Math.min(
                        dp[i - 1][j],     // deletion
                        dp[i][j - 1],     // insertion
                        dp[i - 1][j - 1]  // substitution
                    );
                }
            }
        }

        return dp[m][n];
    };


    // display each equation's edit distance with every other,MX_EQUATIONS
    const dpMxe = (mxe) => {

        const dsMap = {};
        for (const i of mxe) {
            dsMap[i] = [];
            for (const j of mxe) {
                dsMap[i].push({ "eq": j, "d": levenshteinDistance(i, j) });
            }

            dsMap[i].sort((a, b) => a.d - b.d);
        }

        let s = "";
        for (const i in dsMap) {
            s += "<div>" + i + ":<br>"

            for (let j = 1; j < 20; j++) {
                if (dsMap[i][j].d > 6) break;
                s += dsMap[i][j].eq + "; " + dsMap[i][j].d + "<br>";
            }
            s += "</div>";
        }

        const D = document.createElement('div');
        D.id = "D4";
        D.innerHTML = s;
        document.body.appendChild(D);
    };

    /**
     * minus two equations
     * @param {string} equation a 
     * @param {string} equation b
     * @returns {string} if error return "Error", ERR_STR
     */
    const misEq1 = (a, b) => {
        const [aL, aR] = a.split("=");
        const [bL, bR] = b.split("=");

        if (aR !== bR) return ERR_STR;

        let pir = [];

        const aS2 = aL.split("+");
        const bS2 = bL.split("+");

        for (let i = 0; i < MX_LENGTH; i++) {
            const aS = aS2[i].split(CD);
            const bS = bS2[i].split(CD);

            if (aS[0] == bS[0]) {
                if (MX_P_ORD.get(aS[1]) < MX_P_ORD.get(bS[1])) {
                    pir.push(`${aS[0]}${CD}(${aS[1]}-${bS[1]})`);
                } else {
                    pir.push(`${aS[0]}${CD}(${bS[1]}-${aS[1]})`);
                }
            } else if (aS[1] == bS[1]) {
                if (MX_P_ORD.get(aS[0]) < MX_P_ORD.get(bS[0])) {
                    pir.push(`${aS[1]}${CD}(${aS[0]}-${bS[0]})`);
                } else {
                    pir.push(`${aS[1]}${CD}(${bS[0]}-${aS[0]})`);
                }
            } else {
                return ERR_STR;
            }
        }

        return pir.join("+") + "=0";
    }

    /**
     * Generates equations from matrix expression
     * @param {string} mxe : Matrix expression
     * @returns {string} Equations
     */
    const gnrEq1 = (mxe, dis = 3) => {

        let eqs = [];
        const l = mxe.length;
        for (let i = 0; i < l; i++) {
            for (let j = i; j < l; j++) {
                if (levenshteinDistance(mxe[i], mxe[j]) !== dis) continue;
                eqs.push(misEq1(mxe[i], mxe[j]));
            }
        }

        return eqs;
    };

    const MX_EQUATIONS_2 = gnrEq1(MX_EQUATIONS_1, MX_LENGTH);
    // console.log("Error in MX_EQAUTIONS_2", MX_EQUATIONS_2.some(eq => eq === ERR_STR));

    // for sort MX_EQUATIONS_2 like 'B2•(E0-F0)+B5•(E1-F1)+B8•(E2-F2)=0'
    const PARSE_EQ2 = [0, 11, 22, 4, 7, 15, 18, 26, 29];
    const getParse2 = (s, i, l = 2) => s.substr(PARSE_EQ2[i], l);
    MX_EQUATIONS_2.sort((a, b) => {
        let f = 0;
        for (let i = 0; i < PARSE_EQ2.length; i++) {
            f += MX_P_ORD.get(getParse2(a, i)) - MX_P_ORD.get(getParse2(b, i));
            f = f << 2;
        }

        return f;
    });

    //
    const MX_LINERS_2 = ((x) => {
        if (!x) return;
        const liner = {};
        MX_EQUATIONS_2.forEach((eq) => {
            let [a0, a1, a2] = [getParse2(eq, 0), getParse2(eq, 1), getParse2(eq, 2)];

            let key = `${a0},${a1},${a2}`;
            if (!liner[key]) {
                liner[key] = [];
            }

            liner[key].push([getParse2(eq, 3, 5), getParse2(eq, 5, 5), getParse2(eq, 7, 5)]);
        });

        return liner;
    })(true);


    const minSm1 = (s) => {
        let [p1, p2, p3, p4] =
            [s.substr(2, 2), s.substr(5, 2), s.substr(10, 2), s.substr(13, 2)];

        if (p1 === p3) {
            return [p2, p4];
        }
        if (p1 === p4) {
            return [p2, p3];
        }
        if (p2 === p3) {
            return [p1, p4];
        }
        if (p2 === p4) {
            return [p1, p3];
        }
        // impossible be here
        if (p1 === p2) {
            return [p3, p4];
        }
        if (p3 === p4) {
            return [p1, p2];
        }
    }


    const MX_EQUATIONS_3 = ((x) => {
        if (!x) return [];

        const eqM = new Map();
        let eqs1 = gnrEq1(MX_EQUATIONS_2, MX_LENGTH);
        eqs1 = eqs1.concat(gnrEq1(MX_EQUATIONS_2, 2 * MX_LENGTH));

        const l0 = 7; // lenth of (A0-D0)
        /*
         * fix length 64 
         * like '(A0-D0)•(A0-E0)+(A3-D3)•(A1-E1)+(A6-D6)•(A2-E2)=0' should save,
         * like 'E2•((A3-C3)-(B3-C3))+E5•((A4-C4)-(B4-C4))+E8•((A5-C5)-(B5-C5))=0' shuould filter;
         */
        const l1 = (l0 * 2 + 1) * MX_LENGTH + MX_LENGTH - 1 + 2;
        eqs1 = eqs1.filter(eq => eq.length == l1);

        let eqs2 = gnrEq1(eqs1);
        // console.log("Error in eqs2", eqs2.some(eq => eq === ERR_STR));

        eqs1 = eqs1.map(eq => eq.substring(0, l1 - 2));

        eqs1.forEach(eq => {
            let i = 0;
            for (const p of eq.split("+")) {
                let j = 1;
                for (const q of p.split(CD)) {
                    let [x1, x2] = [q.substr(1, 2), q.substr(4, 2)];
                    j *= MX_P_ORD.get(x1) * MX_P_ORD.get(x2);
                }
                i += j;
            }
            if (eqM.has(i)) {
                eqM.get(i).push(eq);
            } else {
                eqM.set(i, [eq]);
            }
        });

        /*
         * fix length like
         * '(A6-B6)•((A0-E0)-(A0-F0))+(A7-B7)•((A3-E3)-(A3-F3))+(A8-B8)•((A6-E6)-(A6-F6))=0'
         */
        const l2 = (l0 * 3 + 4) * MX_LENGTH + MX_LENGTH - 1 + 2;
        eqs2 = eqs2.map(eq => eq.substring(0, l2 - 2));

        eqs2.forEach(eq => {
            let i = 0, eq2 = [];
            let x1, x2;
            for (const p of eq.split("+")) {
                let j = 1, ss = '';
                let [a, b] = p.split(CD);
                if (REG_1.test(a)) {
                    j *= MX_P_ORD.get(a.substr(1, 2)) * MX_P_ORD.get(a.substr(4, 2));

                    [x1, x2] = minSm1(b);
                    j *= MX_P_ORD.get(x1) * MX_P_ORD.get(x2);
                } else if (REG_1.test(b)) {
                    console.info('type b', eq);

                    j *= MX_P_ORD.get(b.substr(1, 2)) * MX_P_ORD.get(b.substr(4, 2));

                    [x1, x2] = minSm1(a);
                    j *= MX_P_ORD.get(x1) * MX_P_ORD.get(x2);
                } else {
                    console.warn('type c', eq);
                    continue;
                }

                ss = MX_P_ORD.get(x1) - MX_P_ORD.get(x2) > 0 ? `(${x2}-${x1})` : `(${x1}-${x2})`;
                eq2.push(a + CD + ss);

                i += j;
            }

            if (!i) {
                console.warn('type d', eq);
            }

            if (eqM.has(i)) {
                eqM.get(i).push(eq2.join('+'));
            } else {
                eqM.set(i, [eq2.join('+')]);
            }
        });

        let rtn = Array.from(eqM.values());

        let f = 0;
        rtn.forEach(et => {
            if (et.length === 1) return null;

            let pairs1 = [];
            let x1, x2;

            for (const p of et[0].split("+")) {
                let j = 1;
                for (const q of p.split(CD)) {
                    [x1, x2] = [q.substr(1, 2), q.substr(4, 2)];
                    j *= MX_P_ORD.get(x1) * MX_P_ORD.get(x2)
                }
                pairs1.push(j);
            }

            pairs1.sort((a, b) => a - b);

            let pairs2 = [];
            for (let i = 1; i < et.length; i++) {
                pairs2.length = 0;
                for (const p of et[i].split("+")) {
                    let j = 1;
                    for (const q of p.split(CD)) {
                        [x1, x2] = [q.substr(1, 2), q.substr(4, 2)];
                        j *= MX_P_ORD.get(x1) * MX_P_ORD.get(x2)
                    }
                    pairs2.push(j);
                }

                pairs2.sort((a, b) => a - b);

                for (let k = 0; k < MX_LENGTH; k++) {
                    if (pairs2[k] !== pairs1[k]) {
                        console.info('more than 2', et);
                        debugger;
                        f = 1;
                        break;
                    }
                }
            }

        });

        if (f === 0) {
            rtn = rtn.map(e => e[0]);
        }

        return rtn;

    })(false);
    // console.log("Error in MX_EQAUTIONS_3", MX_EQUATIONS_3.some(eq => eq === ERR_STR));

    // console.dir(MX_EQUATIONS_3);
    // dpMxe(MX_EQUATIONS_3);

    MX_EQUATIONS_3.sort((a, b) => a.localeCompare(b));

</script>

</html>